
#include <string.h>
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "board.h"
#include "lcd.h"

#define LCD_CMD_BASE    ((uint32_t)(0x6C000000 | 0x00000000))
#define LCD_DAT_BASE    ((uint32_t)(0x6C000000 | 0x00002000))

#define LCD_CMD         (*(volatile uint16_t *) LCD_CMD_BASE )
#define LCD_DAT         (*(volatile uint16_t *) LCD_DAT_BASE)

#define LCD_BACK_PORT   GPIOF
#define LCD_BACK_PIN    GPIO_Pin_10

#define  WHITE      0xFFFF
#define  BLACK      0x0000
#define  RED        0xF800
#define  GREEN      0x07E0
#define  BLUE       0x001F
#define  BRED       0XF81F
#define  GRED       0XFFE0
#define  GBLUE      0X07FF
#define  BROWN      0XBC40
#define  BRRED      0XFC07
#define  GRAY       0X8430
#define  MAGENTA    0xF81F
#define  CYAN       0x7FFF
#define  YELLOW     0xFFE0

#define DARKBLUE    0X01CF
#define LIGHTBLUE   0X7D7C
#define GRAYBLUE    0X5458
#define LIGHTGREEN  0X841F
#define LIGHTGRAY   0XEF5B
#define LGRAY       0XC618
#define LGRAYBLUE   0XA651
#define LBBLUE      0X2B12

typedef struct
{
    uint16_t    width;
    uint16_t    height;
    uint16_t    id;         // lcd controller id
    uint8_t     dir;        // 0 -- normal, 1 -- 90', 2 -- 180', 3 -- 270'
    uint16_t    wramcmd;    // write gram command
    uint16_t    setxcmd;    // set x command
    uint16_t    setycmd;    // set y command
} lcd_info_t;

static lcd_info_t lcd_dev;

int LCD_GetInfo(uint16_t *width, uint16_t *height, uint8_t *direction)
{
    if (width) {
        *width = lcd_dev.width;
    }
    if (height) {
        *height = lcd_dev.height;
    }
    if (direction) {
        *direction = lcd_dev.dir;
    }
    return 0;
}

void LCD_WriteReg(uint16_t LCD_Reg, uint16_t LCD_Value)
{
    LCD_CMD = LCD_Reg;
    LCD_DAT = LCD_Value;
}

uint16_t LCD_ReadReg(uint16_t LCD_Reg)
{
    LCD_CMD = LCD_Reg;
    for (volatile int i = 0; i < 64; i++) // Add delay to wait for the LCD to process the command
    {
        __NOP();
    }
    return LCD_DAT;
}

static uint16_t LCD_ReadId(void)
{
    uint16_t id;

    LCD_CMD = 0xD3;
    id = LCD_DAT;
    id = LCD_DAT;   //0x00
    id = LCD_DAT;   //0x93
    id <<= 8;
    id |= LCD_DAT;  //0x41

    return id;
}

static void LCD_SetWindow(uint16_t startX, uint16_t startY, uint16_t endX, uint16_t endY)
{
    LCD_CMD = lcd_dev.setxcmd;
    LCD_DAT = startX >> 8;
    LCD_DAT = startX & 0xFF;
    LCD_DAT = endX >> 8;
    LCD_DAT = endX & 0xFF;

    LCD_CMD = lcd_dev.setycmd;
    LCD_DAT = startY >> 8;
    LCD_DAT = startY & 0xFF;
    LCD_DAT = endY >> 8;
    LCD_DAT = endY & 0xFF;
}

int LCD_BackLightOp(int on_off)
{
    if (on_off) {
        GPIO_SetBits(LCD_BACK_PORT, LCD_BACK_PIN);
    } else {
        GPIO_ResetBits(LCD_BACK_PORT, LCD_BACK_PIN);
    }
    return 0;
}

int LCD_Init(uint8_t direction)
{
    lcd_dev.id =  LCD_ReadId();
    lcd_dev.wramcmd = 0x2C;
    lcd_dev.setxcmd = 0x2A;
    lcd_dev.setycmd = 0x2B;

    LCD_CMD = 0xCF;
    LCD_DAT = 0x00;
    LCD_DAT = 0xC1;
    LCD_DAT = 0X30;
    LCD_CMD = 0xED;
    LCD_DAT = 0x64;
    LCD_DAT = 0x03;
    LCD_DAT = 0X12;
    LCD_DAT = 0X81;
    LCD_CMD = 0xE8;
    LCD_DAT = 0x85;
    LCD_DAT = 0x10;
    LCD_DAT = 0x7A;
    LCD_CMD = 0xCB;
    LCD_DAT = 0x39;
    LCD_DAT = 0x2C;
    LCD_DAT = 0x00;
    LCD_DAT = 0x34;
    LCD_DAT = 0x02;
    LCD_CMD = 0xF7;
    LCD_DAT = 0x20;
    LCD_CMD = 0xEA;
    LCD_DAT = 0x00;
    LCD_DAT = 0x00;
    LCD_CMD = 0xC0;
    LCD_DAT = 0x1B;
    LCD_CMD = 0xC1;
    LCD_DAT = 0x01;
    LCD_CMD = 0xC5;
    LCD_DAT = 0x30;
    LCD_DAT = 0x30;
    LCD_CMD = 0xC7;
    LCD_DAT = 0XB7;
    LCD_CMD = 0x36;
    LCD_DAT = 0x48;
    LCD_CMD = 0x3A;
    LCD_DAT = 0x55;
    LCD_CMD = 0xB1;
    LCD_DAT = 0x00;
    LCD_DAT = 0x1A;
    LCD_CMD = 0xB6;
    LCD_DAT = 0x0A;
    LCD_DAT = 0xA2;
    LCD_CMD = 0xF2;
    LCD_DAT = 0x00;
    LCD_CMD = 0x26;
    LCD_DAT = 0x01;
    LCD_CMD = 0xE0;
    LCD_DAT = 0x0F;
    LCD_DAT = 0x2A;
    LCD_DAT = 0x28;
    LCD_DAT = 0x08;
    LCD_DAT = 0x0E;
    LCD_DAT = 0x08;
    LCD_DAT = 0x54;
    LCD_DAT = 0XA9;
    LCD_DAT = 0x43;
    LCD_DAT = 0x0A;
    LCD_DAT = 0x0F;
    LCD_DAT = 0x00;
    LCD_DAT = 0x00;
    LCD_DAT = 0x00;
    LCD_DAT = 0x00;
    LCD_CMD = 0XE1;
    LCD_DAT = 0x00;
    LCD_DAT = 0x15;
    LCD_DAT = 0x17;
    LCD_DAT = 0x07;
    LCD_DAT = 0x11;
    LCD_DAT = 0x06;
    LCD_DAT = 0x2B;
    LCD_DAT = 0x56;
    LCD_DAT = 0x3C;
    LCD_DAT = 0x05;
    LCD_DAT = 0x10;
    LCD_DAT = 0x0F;
    LCD_DAT = 0x3F;
    LCD_DAT = 0x3F;
    LCD_DAT = 0x0F;
    LCD_CMD = 0x2B;
    LCD_DAT = 0x00;
    LCD_DAT = 0x00;
    LCD_DAT = 0x01;
    LCD_DAT = 0x3f;
    LCD_CMD = 0x2A;
    LCD_DAT = 0x00;
    LCD_DAT = 0x00;
    LCD_DAT = 0x00;
    LCD_DAT = 0xef;
    LCD_CMD = 0x11;
    hw_delay_ms(120);
    LCD_CMD = 0x29;

    LCD_SetDirection(direction);
    LCD_BackLightOp(1);
    LCD_Clear(WHITE);

    return 0;
}

int LCD_Clear(uint16_t color)
{
    int pointnum = lcd_dev.width * lcd_dev.height;
    LCD_SetWindow(0, 0, lcd_dev.width - 1, lcd_dev.height - 1);

    LCD_CMD = lcd_dev.wramcmd;
    for(int i = 0; i < pointnum; i ++)
    {
        LCD_DAT = color;
    }

    return 0;
}

int LCD_SetDirection(uint8_t direction)
{
    switch(direction)
    {
        case 0:     // 0', portait
            lcd_dev.width = 240;
            lcd_dev.height = 320;
            // BGR=1,MY=0,MX=0,MV=0: no flip, no rotation
            LCD_CMD = 0x36;
            LCD_DAT = ((1<<3) | (0<<6) | (0<<7));
            lcd_dev.dir = direction;
            break;

        case 1:     // 90', landscape
            lcd_dev.width = 320;
            lcd_dev.height = 240;
            // BGR=1,MY=0,MX=1,MV=1: X mirror, row/column exchange
            LCD_CMD = 0x36;
            LCD_DAT = ((1<<3) | (0<<7) | (1<<6) | (1<<5));
            lcd_dev.dir = direction;
            break;

        case 2:     // 180', mirror portait
            lcd_dev.width = 240;
            lcd_dev.height = 320;
            // BGR=1,MY=1,MX=1,MV=0: X and Y mirror
            LCD_CMD = 0x36;
            LCD_DAT = ((1<<3) | (1<<6) | (1<<7));
            lcd_dev.dir = direction;
            break;

        case 3:     // 270', mirror landscape
            lcd_dev.width = 320;
            lcd_dev.height = 240;
            // BGR=1,MY=1,MX=0,MV=1: Y mirror, row/column exchange
            LCD_CMD = 0x36;
            LCD_DAT = ((1<<3) | (1<<7) | (1<<5));
            lcd_dev.dir = direction;
            break;

        default:  // default portait
            lcd_dev.width = 240;
            lcd_dev.height = 320;
            // BGR=1,MY=0,MX=0,MV=0: no flip, no rotation
            LCD_CMD = 0x36;
            LCD_DAT = ((1<<3) | (0<<6) | (0<<7));
            lcd_dev.dir = 0;
            break;
    }
    return 0;
}

int LCD_FillBlock(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t *colors)
{
    uint16_t width = x2 - x1 + 1;
    uint16_t height = y2 - y1 + 1;

    LCD_SetWindow(x1, y1, x2, y2);
    LCD_CMD = lcd_dev.wramcmd;

    for (uint16_t i = 0; i < height; i++) {
        for (uint16_t j = 0; j < width; j ++) {
            LCD_DAT = *colors ++;
        }
    }
    return 0;
}

int LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color)
{
    LCD_SetWindow(x, y, x, y);
    LCD_CMD = lcd_dev.wramcmd;
    LCD_DAT = color;
    return 0;
}

int LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{

    int incx = 0;
    int delta_x = (int)x2 - (int)x1;
    if (delta_x > 0) {
        incx = 1;
    } else if (delta_x == 0) {
        incx = 0;
    } else {
        incx = -1;
        delta_x = -delta_x;
    }

    int incy = 0;
    int delta_y = (int)y2 - (int)y1;
    if(delta_y > 0) {
        incy = 1;
    } else if(delta_y == 0) {
        incy = 0;
    } else {
        incy = -1;
        delta_y = -delta_y;
    }

    int distance = delta_x > delta_y ? delta_x : delta_y;
    int xerr = 0, yerr = 0, uRow = x1, uCol = y1;
    for(uint16_t t = 0; t <= distance + 1; t ++)
    {
        LCD_DrawPoint(uRow, uCol, color);
        xerr += delta_x ;
        yerr += delta_y ;
        if(xerr > distance)
        {
            xerr -= distance;
            uRow += incx;
        }
        if(yerr > distance)
        {
            yerr -= distance;
            uCol += incy;
        }
    }
    return 0;
}

int LCD_DrawFilledCircle(uint16_t centerX, uint16_t centerY, uint16_t radius, uint16_t color)
{
    int16_t x = 0;
    int16_t y = radius;
    int16_t d = 1 - radius;
    int16_t deltaE = 3;
    int16_t deltaSE = -2 * radius + 5;

    LCD_DrawLine(centerX - radius, centerY, centerX + radius, centerY, color);

    while (y > x)
    {
        if (d < 0)  // east
        {
            d += deltaE;
            deltaE += 2;
            deltaSE += 2;
        }
        else       // south east
        {
            d += deltaSE;
            deltaE += 2;
            deltaSE += 4;
            y--;
        }
        x++;

        LCD_DrawLine(centerX - x, centerY + y, centerX + x, centerY + y, color);
        LCD_DrawLine(centerX - x, centerY - y, centerX + x, centerY - y, color);
        LCD_DrawLine(centerX - y, centerY + x, centerX + y, centerY + x, color);
        LCD_DrawLine(centerX - y, centerY - x, centerX + y, centerY - x, color);
    }
    return 0;
}
